package gesconda.CBRKBRecommender.model;

//import gesconda.clustering.vista.*;
//import gesconda.clustering.vista.dialegs.*;

import gesconda.CBR.model.CMAttributeUtility;
import gesconda.master.model.CMAtribut;
import gesconda.master.model.CMAtributContinu;
import gesconda.master.model.CMAtributDiscret;
import gesconda.master.model.CMAtributDiscretNoOrdenat;
import gesconda.master.model.CMAtributDiscretOrdenat;
import gesconda.master.model.CMInfoAtribut;
import gesconda.master.model.CMInfoAtributContinu;
import gesconda.master.model.CMInstancia;
import gesconda.master.model.CMModel;
import gesconda.master.model.CMValor;
import gesconda.master.model.CMValorContinu;
import gesconda.master.model.CMValorDiscret;
import gesconda.master.model.CMValorDiscretNoOrdenat;
import gesconda.master.model.CMValorDiscretOrdenat;
import gesconda.master.vista.Desktop;
import gesconda.master.vista.Messages;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

import javax.swing.table.AbstractTableModel;

public class CMTaulaMethodChar extends AbstractTableModel {

	/**
	 * Llista d'instàncies de la BD
	 */
	private List<CMInstancia> instancies = new ArrayList<CMInstancia>();

	/**
	 * LLista d'atributs de la BD
	 */
	private List<CMAtribut> atributs;

	private CMModel model;



	/**
	 * atribut classe, per a saber quina es la classe del
	 * conjunt d'instancies que te la taula
	 */
	private CMAtributDiscret atributClasse = null;


	public CMTaulaMethodChar(List<CMInstancia> inst, List<CMAtribut> atrib) {
		this.instancies = inst;
		this.atributs = atrib;
	}


	public int getNumInstancies() {
		return this.instancies.size();
	}

	public int getNumAtributs() {
		return atributs.size();
	}

	/**
	 * Afegeix un atribut a la taula
	 * @param a
	 * @param obs
	 */
	public CMAtribut addAtribut(CMAtribut a, Vector obs) {
		int contador = 1;
		String nomAtribut = a.getNom();
		while (this.getAtribut(nomAtribut) != null) {
			nomAtribut = a.getNom() + "_" + contador;
			contador++;
		}
		if (contador > 1) {
			a.setNom(nomAtribut);
		}
		
		this.atributs.add(a);
		if (obs != null) {
			for (int i = 0; i < obs.size(); i++) {
				CMInstancia inst;
				if (i < this.instancies.size()) {
					inst = this.instancies.get(i);
				} else {
					inst = new CMInstancia(Integer.toString(i+1));
					this.instancies.add(inst);
				}
				if(obs.get(i)==null)
					inst.afegirValorAtribut(a, "?");
				else
					inst.afegirValorAtribut(a, obs.get(i).toString());
			}
		}
		return a;
//		fireTableStructureChanged();
	}

	public CMInstancia getInstancia(int posicio) {
		return instancies.get(posicio);
	}


	/**
	 * Llista d'atribus de la taula
	 * @return
	 */
	public List<CMAtribut> getAtributs() {
		return this.atributs;
	}

	/**
	 * Retorna l'atribut a la posició i
	 * @param i
	 * @return
	 */
	public CMAtribut getAtribut(int i) {
		return atributs.get(i);
	}

	/**
	 * Retorna un atribut de la taula per nom.
	 * Si no el troba retorna nul.
	 *
	 * @param nomAtribut
	 * @return CMAtribut
	 */
	public CMAtribut getAtribut(String nomAtribut) {
		for (CMAtribut a : this.atributs) {
			if (a.getNom().equals(nomAtribut))
				return a;
		}
		return null;
	}

	public List<CMInstancia> getInstancies() {
		return this.instancies;
	}

	public void addInstancia(CMInstancia i) {
		this.instancies.add(i);
//		fireTableDataChanged();
	}

	@Override
	public String toString() {
		return Messages.getString("DATABASE")+" (" + this.instancies.size() + " "+Messages.getString("DataView.instancies")+")";
	}

	public CMInfoAtribut getInfoAtribut(int i) {
		return atributs.get(i).getInfoAtribut();
	}

	public boolean hiHaAtributsActius() {
		boolean res = false;
		int i = 0;
		while ((!res) && (i < atributs.size()))
			res = res || atributs.get(i++).esActiu();
		return res;
	}

	/**
	 * Donat un atribut, calcula la informació acumulada a partir dels valors de
	 * totes les intàncies de la taula.
	 *
	 * @param atribut
	 *            atribut al que se li recalcula la informació.
	 */
	public void calcularInformacioAtribut(CMAtribut atribut) {
		atribut.getInfoAtribut().resetInfo();
		for (int i = 0; i < this.instancies.size(); i++) {
			CMValor valor = getInstancia(i).getValor(atribut.getPosicio());
			atribut.getInfoAtribut().actualitzarAtribut(valor);
		}
	}

//	public CMModel getModel() {
//		return this.model;
//	}


	public boolean importarCLS(File fitxer, boolean capsalera) {
		return true;
	}



	public boolean existeixNomAtribut(String nom) {
		boolean res = false;
		int i = 0;
		while ((!res) && (i < atributs.size()))
			res = atributs.get(i++).getNom().toUpperCase().equals(nom.toUpperCase());

		return res;
	}



	/** Estableix l'atibut classe */
	public void setAtributClasse(CMAtribut atribut) {
		CMAtributDiscret oldValue = this.atributClasse;
		this.atributClasse = (CMAtributDiscret) atribut;
//		this.getModel().pcs.firePropertyChange("classe", oldValue, atribut);
		//buildPrototips();
		fireTableStructureChanged();
		Desktop.getInstance().showStatus();
	}

	public void setAtributClasseString(String atribut) {
		setAtributClasse(this.getAtribut(atribut));
	}

	public String getNomAtributClasse() {
		if (atributClasse == null)
			return null;
		else
			return atributClasse.getNom();
	}


	/**
	 * Retorna l'atribut classe o nul si no n'hi ha cap
	 * @return
	 */
	public CMAtributDiscret getAtributClasse() {
			return atributClasse;
	}

	/**
	 * Retorna la posició de l'atribut o -1 si no hi és.
	 * @param a atribut de la taula
	 * @return posició
	 */
	public int getPosicioAtribut(CMAtribut a) {
		return this.atributs.indexOf(a);
	}


	public int getPosicioAtribut(String nom) {
		for(int pos=0; pos<this.getNumAtributs(); pos++) {
			if(getAtribut(pos).getNom().equals(nom))
				return pos;
		}
		throw new Error(Messages.getString("NO_VARIABLES")+nom);
	}

	public int getPosicioAtributClasse() {
		if(atributClasse==null)
			throw new Error(Messages.getString("NO_CLASS_VARIABLE"));
		return getPosicioAtribut(atributClasse);
	}

	/**
	 * Fa una copia de la taula, però sense incloure la variable classe
	 * @return
	 */
	public CMTaulaMethodChar eliminaClasse() {
		CMTaulaMethodChar taula2 = new CMTaulaMethodChar(new Vector<CMInstancia>(), new Vector<CMAtribut>());

		int posAtributClasse = getPosicioAtributClasse();
		CMInstancia inst2;
		// afegim les instancies
		for (int j = 0; j < getNumInstancies(); j++) {
			CMInstancia inst = getInstancia(j);
			inst2 = new CMInstancia();
			inst2 = inst2.eliminaAtributValor(inst, posAtributClasse);
			taula2.afegeixInstancia(inst2);
		}

		// canviem el vector atributs
		for (int i = 0; i < atributs.size(); i++) {
			if (i != posAtributClasse) {
				taula2.addAtribut(atributs.get(i), null);
			}
		}
		return taula2;
	}

	/**
	 * Elimina un atribut i totes les seves observacions de la BD
	 * @param atribut de la taula a eliminar
	 *
	 */
	protected void eliminaAtribut(CMAtribut a) {

		for(CMInstancia i: getInstancies()) {
			i.eliminaValor(a.getPosicio());
		}

		this.atributs.remove(a);

		if(getAtributClasse()==a)
			setAtributClasse(null);

//		fireTableStructureChanged();
	}


	// aquesta funcio l'usa la validacio simple i creuada per a crear les taules
	// test i training set
	// posem instancia_activa a 1? hauriem de ignorar instancia o substituir-la
	// segons el q hagi fet
	// l'usuari abans??
	public void afegeixInstancia(CMInstancia inst) {
		instancies.add(inst);
//		fireTableDataChanged();
	}


	/**
	 * Desactiva les instancies que contenen missings
	 *
	 */
	public void ignorarMissings() {
		// fem un recorregut per les instancies de la classe
		for (CMInstancia instancia: this.getInstancies()) {

			// Si conté missings la desactivem, i sinó l'activem.
			if(instancia.conteMissings()) {
				instancia.setActiva(false);
			} else {
				instancia.setActiva(true);
			}
		}
	}

	// substitueix tots els missings que pugui haver a la taula per la moda
	public void substituirMissings() {
		// fem un recorregut sobre els atributs del vector atributs
		for (int i = 0; i < atributs.size(); i++) {
			// per a cada atribut, calculem la seva moda

			if (atributs.get(i).getTipus() == CMAtribut.Tipus.CONTINU) {
				// atribut continu (float)
				// fem un array de strings amb tots els valors que ha pres
				// l'atribut i
				String[] valors = (((CMAtributContinu) atributs.get(i)).getInfoAtribut()).getValorsExistents();
				int[] comptadors = new int[valors.length];
				for (int k = 0; k < comptadors.length; k++)
					comptadors[k] = 0;

				for (int j = 0; j < instancies.size(); j++) {
					String val = " ";
					val = instancies.get(j).getValor(i).getValorString();
					// cerquem val a l'array valors
					boolean trobat = false;
					int z = 0;
					while (!trobat && (z < valors.length)) {
						if (valors[z].equals(val)) {
							comptadors[z] = comptadors[z] + 1;
							trobat = true;
						} else
							z++;
					}
				}
				// cerquem el maxim de comptadors, no pot ser que el maxim
				// correspongui a '?', un missing
				int max = 0;
				for (int w = 0; w < comptadors.length; w++) {
					String missing = "?";
					if (comptadors[w] >= comptadors[max] && (valors[w].equals(missing) == false))
						max = w;
				}
				// substituim tots els missings d'aquell atribut per la moda:
				// el valor pel valor corresponent a la posicio j
				String nou_valor = " ";
				nou_valor = valors[max];
				/*
				 * Integer val_int = new Integer(nou_valor); int val_int2 =
				 * val_int.intValue();
				 */
				int val_int2 = (int) Float.parseFloat(nou_valor);
				// ara hem de calcular la mitjana del rang val_int2
				float rang_sup = ((CMValorContinu) instancies.get(0).getValor(i)).retornaRangSuperior(val_int2);
				float rang_inf = ((CMValorContinu) instancies.get(0).getValor(i)).retornaRangInferior(val_int2);
				float mitjana = Math.abs(rang_sup + rang_inf) / 2;
				mitjana = rang_inf + mitjana;
				for (int j = 0; j < instancies.size(); j++) {
					if ((instancies.get(j).getValor(i)).esMissing()) {
						instancies.get(j).setActiva(true);
						((CMValorContinu) instancies.get(j).getValor(i)).setValor(mitjana);
						// ara el valor ja no es missing
						((CMValorContinu) instancies.get(j).getValor(i)).setMissing(false);
					}
				}
			} else {
				// atribut discret (String)
				// fem un array de strings amb tots els valors que ha pres
				// l'atribut i
				String[] valors = (((CMAtributDiscret) atributs.get(i)).getInfoAtribut()).getValorsExistents();
				int[] comptadors = new int[valors.length];
				for (int k = 0; k < comptadors.length; k++)
					comptadors[k] = 0;
				for (int j = 0; j < instancies.size(); j++) {
					String val = " ";
					val = ((CMValorDiscret) instancies.get(j).getValor(i)).getValorString();
					// cerquem val a l'array valors
					boolean trobat = false;
					int z = 0;
					while (!trobat && (z < valors.length)) {
						if (valors[z].equals(val)) {
							comptadors[z] = comptadors[z] + 1;
							trobat = true;
						} else
							z++;
					}
				}
				// cerquem el maxim de comptadors, no pot ser que el maxim
				// correspongui a '?', un missing
				int max = 0;
				for (int j = 0; j < comptadors.length; j++) {
					String missing = "?";
					if (comptadors[j] >= comptadors[max] && (valors[j].equals(missing) == false))
						max = j;
				}

				// substituim tots els missings d'aquell atribut per la moda:
				// el valor pel valor corresponent a la posicio j
				String nou_valor = " ";
				nou_valor = valors[max];
				for (int j = 0; j < instancies.size(); j++) {
					if ((instancies.get(j).getValor(i)).esMissing()) {
						instancies.get(j).setActiva(true);

						((CMValorDiscret) instancies.get(j).getValor(i)).setValor(nou_valor);
						// ara el valor ja no es missing
						((CMValorDiscret) instancies.get(j).getValor(i)).setMissing(false);
					}
				}
			}
		}
	}

	// retorna si els atributs continus de la taula han estat discretitzats o
	// no.
	// si no hi ha atributs continus, retorna true.
	public boolean atributsDiscretitzats() {
		boolean ret = true;
		// fem un recorregut sobre els atributs continus actius del vector atributs
		int i = 0;
		while (ret && i < atributs.size()) {
			if (atributs.get(i).getTipus() == CMAtribut.Tipus.CONTINU && atributs.get(i).esActiu()) {
				if (((CMInfoAtributContinu) atributs.get(i).getInfoAtribut()).getNormalitzat() == false)
					ret = false;
			}
			i++;
		}
		return ret;
	}


	/**
	 * Indica si hem fet un tractament dels missings
	 * Retorna en un boolea si a la taula hi ha alguna instància activa
	 * que té missings en algun dels atributs actius
	 */
	public boolean missingsTractats() {
		// cerquem entre les instancies actives, si hi ha cap que tingui missings
		for(CMInstancia i : this.getInstancies()) {
			if(i.esActiva() && i.conteMissings()) {
				return false;
			}
		}
		return true;
	}


	/* implementació de TableModel */

	public int getColumnCount() {
		return atributs.size() + 1;
	}

	public int getRowCount() {
		if (instancies == null)
			return 0;
		return instancies.size();
	}

	/**
	 * Retorna el valor d'una cel.la. Localitza la instància per número de fila
	 * i retorna el valor de l'atribut de la columna, tenint en compte que la
	 * primera columna és la clau (identificador) de la instància.
	 */
	public Object getValueAt(int rowIndex, int columnIndex) {
		if (columnIndex == 0)
			return getInstancia(rowIndex).getClau();
		return getInstancia(rowIndex).getValor(columnIndex - 1);
	}

	/**
	 * Estableix el valor d'un atribut o la calu en una instància. Implementat
	 * en TableModel, per tant es crida des del controlador la vista
	 * GescondaTable.
	 */
	@Override
	public void setValueAt(Object valor, int fila, int col) {
		if (col == 0) {
			getInstancia(fila).setClau(valor.toString());
			fireTableCellUpdated(fila, col);
		} else {
			CMValor cmvalor = getInstancia(fila).getValor(col - 1);
			System.out.println(Messages.getString("ROW")+" "+fila+": "+Messages.getString("MODIFY_ATTRIBUTE")+" "+cmvalor.getNomAtribut()+" "+Messages.getString("VALUE")+" "+cmvalor.getValorString()+" "+Messages.getString("FOR")+" "+valor);
			String valorAnt = cmvalor.getValorString();
			cmvalor.setValor(valor);
			if(!valorAnt.equals(valor)) {
				// Si ha canviat actualitzem i notifiquem
				System.out.println(Messages.getString("CHANGED")+" "+cmvalor.getValorString());
				calcularInformacioAtribut(this.getAtribut(col - 1));
				fireTableCellUpdated(fila, col);
				this.model.setDirty(true);
			}
		}
	}

	/**
	 * Indica si una cel.la de la taula és editable o no
	 */
	@Override
	public boolean isCellEditable(int fila, int col) {
		// La primera columna conté l'identificador de la instpància.
		if (col == 0)
			return true;

		if (col - 1 < atributs.size())
			return true;
		else
			return false;
	}

	/**
	 * Retorna la classe dels elements a una columna. Necessari per poder
	 * seleccinar un editor
	 */
	@Override
	public Class<?> getColumnClass(int columnIndex) {

		if (columnIndex == 0)
			return String.class;
		Class tipusAtribut = atributs.get(columnIndex - 1).getClass();
		if(tipusAtribut.equals(CMAttributeUtility.class))
			return CMAttributeUtility.class;
		else if (tipusAtribut.equals(CMAtributContinu.class)) {
			return CMValorContinu.class;
		} else if (tipusAtribut.equals(CMAtributDiscretOrdenat.class)) {
			return CMValorDiscretOrdenat.class;
		} else if (tipusAtribut.equals(CMAtributDiscretNoOrdenat.class)) {
			return CMValorDiscretNoOrdenat.class;
		} else
			return Object.class;
	}

	@Override
	/**
	 * Nom de la columna (de l'atribut que mostra)
	 */
	public String getColumnName(int column) {
		if (column == 0)
			return " ";
		String text = atributs.get(column - 1).getNom();
		return text;
	}

	/* fi implementació de TableModel */


//	/**
//	 * Afegeix una nova instancia buida a la Base de Dades i notifica a la vista
//	 * d'aquest fet.
//	 */
//	public void addEmptyRow() {
//		instancies.add(new CMInstancia());
//		fireTableRowsInserted(instancies.size() - 1, instancies.size() - 1);
//	}

	/**
	 * Esborra el contingut de la taula de dades i notifica a les vistes
	 */
	void clear() {
		this.atributs = new Vector<CMAtribut>();
		this.instancies = new Vector<CMInstancia>();
		this.atributClasse=null;
		fireTableStructureChanged();
	}

	/**
	 * Retorna en una cadena la taula de dades en format XML
	 * tag "data"
	 * @return StringBuffer
	 */
	public StringBuffer toXML() {
		StringBuffer sb = new StringBuffer();
		sb.append("<data>\n");
		sb.append(" <attributes>\n");
		for (int i = 0; i < atributs.size(); i++) {
			CMAtribut a = atributs.get(i);
			sb.append("  ");
			sb.append(a.toXML());
			sb.append("\n");
		}
		sb.append(" </attributes>\n");
		sb.append(" <instances>\n");
		for (CMInstancia inst: this.instancies) {
			sb.append(inst.toXML());
		}
		sb.append(" </instances>\n");
		sb.append("</data>\n");
		return sb;
	}


	@Override
	/** Notifica a la vista per refrescar la taula */
	public void fireTableStructureChanged() {
		super.fireTableStructureChanged();
		// Repassem els atributs carregats per amagar els inactius
		for(CMAtribut atribut: getAtributs()) {
			if (!atribut.esActiu()) {
				// Treure la columna
				Desktop.getInstance().amagaAtribut(atribut);
			}
		}
	}



}